/**
 * @file lv_port_disp_templ.c
 *
 */

/*Copy this file as "lv_port_disp.c" and set this value to "1" to enable content*/
#if 1

/*********************
 *      INCLUDES
 *********************/
#include "lv_port_disp.h"
#include <stdbool.h>
#include "lcd_drv.h"
//屏幕宽度
#define MY_DISP_HOR_RES (320)
//屏幕高度
#define MY_DISP_VER_RES (480)

/*********************
 *      DEFINES
 *********************/
#ifndef MY_DISP_HOR_RES
#warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen width, default value 320 is used for now.
#define MY_DISP_HOR_RES    320
#endif

#ifndef MY_DISP_VER_RES
#warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen height, default value 240 is used for now.
#define MY_DISP_VER_RES    240
#endif
#define LVGL_USE_DMA 1
#if LVGL_USE_DMA
#define RCU_DMA_X 		RCU_DMA0
#define DMA_X 			  DMA0
#define DMA_CHANNEL		DMA_CH1
#define LcdDataAdress          0x64000002     //lcd数据地址 供dma搬运数据使用
static void LvglDmaConfig(void);
#endif
/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void disp_init(void);
static void disp_flush(lv_disp_drv_t* disp_drv, const lv_area_t* area, lv_color_t* color_p);
//static void gpu_fill(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
//        const lv_area_t * fill_area, lv_color_t color);

/**********************
 *  STATIC VARIABLES
 **********************/

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void lv_port_disp_init(void) {
    /*-------------------------
     * Initialize your display
     * -----------------------*/
    disp_init();
#if LVGL_USE_DMA
    LvglDmaConfig();
#endif
    /*-----------------------------
     * Create a buffer for drawing
     *----------------------------*/

    /*
        *  下面的代码定义了3种缓冲区的方案: 单缓冲、双缓冲、全屏双缓冲
      *	  缓冲区的作用:
        *			 1. 存储图像数据：用于暂存要显示的屏幕图像内容，像绘制的图形、文字、界面元素等，是图像渲染和显示的 “数据容器”，让 LVGL 能在缓冲区里完成图像构建，再输出到屏幕
        *		   2. 支持并行渲染与刷新：比如双缓冲区模式，一个缓冲区用于 LVGL 绘制新内容，另一个缓冲区可同时将已绘制好的内容输出显示，让渲染（绘制新画面）和刷新（显示画面到屏幕 ）并行，提升显示流畅度，减少画面卡顿
            *   缓冲区的3种方案:
            *			单缓冲: 定义一个能存储 10 行像素点颜色数据的缓冲区
            *			双缓冲: 定义两个能存储 10 行像素点颜色数据的缓冲区
            *			全屏双缓冲: 定义两个能存储所有像素点颜色数据的缓冲区
            *  不同的缓冲区方案 占用的内存不一致:
            *			单缓冲占用sram的空间是 320*10 因为是uint16  所以320*10*2 大约是6k多
            *			双缓冲占用sram的空间是 320*10*2 因为是uint16  所以320*10*2*2 大约是12k多
            *			全屏双缓冲占用sram的空间是 320*10*2 因为是uint16  所以320*10*2*2 大约是600k
            *		总结来讲 占用内存越大 显示效果越好
            *   要根据单片机 内存资源来选择
            *   选择具体某一种 就把其他两个注释
     */

    // 单缓冲
//    static lv_disp_draw_buf_t draw_buf_dsc_1;
//    static lv_color_t buf_1[MY_DISP_HOR_RES * 10];
//    lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10);   /*Initialize the display buffer*/

    // 双缓冲 dma 一次最多传输65536个数据(16bit)  65536/380=172行   
		// sram存储空间为512k 16bit  存储两个缓冲区  一个缓冲区最多256k个16bit  256k/380=689行
		// 结合 dma以及sram 最终确定 一个缓冲区保存 172个数据 即可满足dma 一次传输和sram存储
		// 第一个缓冲区的开始地址为0x6C000000 第二个缓冲区的地址为 0x6C000000+172*380*2=0x6C00FF50
		// 这种方式 每次最多刷新 172行 如果需要刷新的部分超过172行 并且与周围的颜色有很大的差别 时 就会导致重影的现象
    static lv_disp_draw_buf_t draw_buf_dsc_2;
    static lv_color_t buf_2_1[MY_DISP_HOR_RES * 172] __attribute__((at(0x6C000000)));                        /*A buffer for 172 rows*/
    static lv_color_t buf_2_2[MY_DISP_HOR_RES * 172] __attribute__((at(0x6C000000+172*MY_DISP_HOR_RES*2)));                /*An other buffer for 172 rows*/
    lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 172);   										 /*Initialize the display buffer*/
	
    // 全屏 双缓冲
    // 占用sram的空间是 320*480 *2 *2 600k
//    static lv_disp_draw_buf_t draw_buf_dsc_3;
//    static lv_color_t buf_3_1[MY_DISP_HOR_RES * MY_DISP_VER_RES] __attribute__((at(0x6C000000)));
//    static lv_color_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES] __attribute__((at(0x6C000000+0x4B000)));
//    lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2,
//                          MY_DISP_VER_RES * MY_DISP_HOR_RES);

    /*-----------------------------------
     * Register the display in LVGL
     *----------------------------------*/

    static lv_disp_drv_t disp_drv;                         /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/

    /*Set up the functions to access to your display*/

    /*Set the resolution of the display*/
    disp_drv.hor_res = MY_DISP_HOR_RES;
    disp_drv.ver_res = MY_DISP_VER_RES;

    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = disp_flush;

    /*Set a display buffer*/
    disp_drv.draw_buf = &draw_buf_dsc_2;

    /*Required for Example 3)*/
    // disp_drv.full_refresh = 1;

    /* Fill a memory array with a color if you have GPU.
     * Note that, in lv_conf.h you can enable GPUs that has built-in support in LVGL.
     * But if you have a different GPU you can use with this callback.*/
    //disp_drv.gpu_fill_cb = gpu_fill;

    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

/*Initialize your display and the required peripherals.*/
static void disp_init(void) {
    /*You code here*/
    LcdDrvInit();
}

volatile bool disp_flush_enabled = true;

/* Enable updating the screen (the flushing process) when disp_flush() is called by LVGL
 */
void disp_enable_update(void) {
    disp_flush_enabled = true;
}

/* Disable updating the screen (the flushing process) when disp_flush() is called by LVGL
 */
void disp_disable_update(void) {
    disp_flush_enabled = false;
}

#if LVGL_USE_DMA
static void LvglDmaConfig(void) {
    /* 使能DMA时钟；*/
    rcu_periph_clock_enable(RCU_DMA_X);
    /* 复位DMA通道；*/
    dma_deinit(DMA_X, DMA_CHANNEL);
    dma_parameter_struct dmaStruct;
    /* 配置传输方向；*/
    dmaStruct.direction = DMA_PERIPHERAL_TO_MEMORY;
    /* 配置数据源地址*/
    // dmaStruct.periph_addr = srcAddress;
    /* 配置源地址是固定的还是增长的；*/
    dmaStruct.periph_inc = DMA_MEMORY_INCREASE_ENABLE;
    /* 配置源数据传输位宽；16 */
    dmaStruct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;
    /* 配置数据目的地址；*/
    // dmaStruct.memory_addr = targetAddress;
    /* 配置目的地址是固定的还是增长的；*/
    dmaStruct.memory_inc = DMA_MEMORY_INCREASE_DISABLE;
    /* 配置目的数据传输位宽；*/
    dmaStruct.memory_width = DMA_MEMORY_WIDTH_16BIT;
    /* 配置数据传输最大次数；*/
//    dmaStruct.number = 153600;
    /* 配置DMA通道优先级；*/
    dmaStruct.priority = DMA_PRIORITY_HIGH;
    // 如果不配置循环模式  达到最大搬运次数就停止  配置循环会把超过的搬运到初始位置
    // dma_circulation_enable(RCU_DMA_X, DMA_CHANNEL);
    dma_init(DMA_X, DMA_CHANNEL, &dmaStruct);
    dma_circulation_disable(DMA_X, DMA_CHANNEL);
    dma_memory_to_memory_enable(DMA_X, DMA_CHANNEL);
    dma_interrupt_enable(DMA_X, DMA_CHANNEL,DMA_INT_FTF);
		dma_interrupt_enable(DMA_X, DMA_CHANNEL,DMA_INT_ERR);
    nvic_irq_enable(DMA0_Channel1_IRQn,1,1);
    dma_channel_disable(DMA_X, DMA_CHANNEL);

}


static volatile lv_disp_drv_t* disp_drv_dma;

/*Flush the content of the internal buffer the specific area on the display
 *You can use DMA or any hardware acceleration to do this operation in the background but
 *'lv_disp_flush_ready()' has to be called when finished.*/
static void disp_flush(lv_disp_drv_t* disp_drv, const lv_area_t* area, lv_color_t* color_p) {
    // 保存驱动指针（原子操作）
    disp_drv_dma = disp_drv;
    uint32_t pixel_cnt = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1);
    LcdFillMultiColorByDma(area->x1, area->y1, area->x2, area->y2);
    dma_channel_disable(DMA_X, DMA_CHANNEL);  // 确保通道已禁用
    dma_periph_address_config(DMA_X, DMA_CHANNEL, (uint32_t)color_p);
    dma_memory_address_config(DMA_X, DMA_CHANNEL, LcdDataAdress);
	  dma_transfer_number_config(DMA_X, DMA_CHANNEL, pixel_cnt);
    dma_interrupt_flag_clear(DMA_X, DMA_CHANNEL, DMA_INT_FLAG_FTF);
	  dma_interrupt_flag_clear(DMA_X, DMA_CHANNEL, DMA_INT_ERR);
    dma_channel_enable(DMA_X, DMA_CHANNEL);
}


void DMA0_Channel1_IRQHandler() {
    if (dma_interrupt_flag_get(DMA_X, DMA_CHANNEL, DMA_INT_FLAG_FTF)) {
        dma_interrupt_flag_clear(DMA_X, DMA_CHANNEL, DMA_INT_FLAG_FTF);
        lv_disp_flush_ready((lv_disp_drv_t*)disp_drv_dma); // 通知LVGL刷新完成
    }else if(dma_interrupt_flag_get(DMA_X, DMA_CHANNEL, DMA_INT_ERR)){
		   dma_interrupt_flag_clear(DMA_X, DMA_CHANNEL, DMA_INT_ERR);
			 printf("dma error \r\n");
		}
}
#else
static void disp_flush(lv_disp_drv_t* disp_drv, const lv_area_t* area, lv_color_t* color_p) {

    LcdFillMultiColor(area->x1, area->y1, area->x2, area->y2,(uint16_t*)color_p);
    lv_disp_flush_ready(disp_drv);
}

#endif

/*OPTIONAL: GPU INTERFACE*/

/*If your MCU has hardware accelerator (GPU) then you can use it to fill a memory with a color*/
//static void gpu_fill(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
//                    const lv_area_t * fill_area, lv_color_t color)
//{
//    /*It's an example code which should be done by your GPU*/
//    int32_t x, y;
//    dest_buf += dest_width * fill_area->y1; /*Go to the first line*/
//
//    for(y = fill_area->y1; y <= fill_area->y2; y++) {
//        for(x = fill_area->x1; x <= fill_area->x2; x++) {
//            dest_buf[x] = color;
//        }
//        dest_buf+=dest_width;    /*Go to the next line*/
//    }
//}


#else /*Enable this file at the top*/

/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif
